package com.graduation.project.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.jwt.JWT;
import cn.hutool.jwt.signers.JWTSignerUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.graduation.project.entity.dto.LoginDTO;
import com.graduation.project.entity.other.ConstantPool;
import com.graduation.project.entity.other.LoginUser;
import com.graduation.project.entity.po.Menu;
import com.graduation.project.entity.vo.MenuDataVO;
import com.graduation.project.entity.vo.MenuTreeVO;
import com.graduation.project.enums.ResultCodeEnum;
import com.graduation.project.mapper.system.MenuMapper;
import com.graduation.project.service.AuthenticationService;
import com.graduation.project.utils.RedisCacheUtil;
import com.graduation.project.utils.ResultUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Service;

import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

/**
 * @author 卑微小峰
 * @date 2022/11/28
 * 用户认证业实现类
 */
@Service
public class AuthenticationServiceImpl implements AuthenticationService {

    @Autowired
    private AuthenticationManager authenticationManager;

    @Autowired
    private RedisCacheUtil redisCacheUtil;

    @Autowired
    private MenuMapper menuMapper;

    /**
     * 用户登录
     * @param loginDTO 用户登录DTO
     * @return ResultUtil
     */
    @Override
    public ResultUtil<?> login(LoginDTO loginDTO) {
        // AuthenticationManager authenticate进行用户认证
        UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(loginDTO.getUserCode(), loginDTO.getUserPassword());
        Authentication authenticate = authenticationManager.authenticate(authenticationToken);
        if (ObjectUtil.isEmpty(authenticate)) {
            throw new RuntimeException("登录失败");
        }
        // 认证通过，使用id和userCode生成一个jwt，返回jwt
        LoginUser loginUser = (LoginUser) authenticate.getPrincipal();
        /**
         * 1.Header 头部信息，主要声明了JWT的签名算法等信息
         * 2.Payload 载荷信息，主要承载了各种声明并传递明文数据
         * 3.Signature 签名，拥有该部分的JWT被称为JWS，也就是签了名的JWS，用于校验数据
         */
        // 当前时间
        long nowMillis = System.currentTimeMillis();
        // 过期时间
        long expMillis = nowMillis + (60 * 60 * 1000L);
        String token = JWT.create()
                .setPayload("id", loginUser.getUser().getId())
                .setPayload("userCode", loginUser.getUser().getUserCode())
                // 设置签发时间
                .setIssuedAt(DateUtil.date(nowMillis))
                // 设置生效时间
                .setNotBefore(DateUtil.date(nowMillis))
                // 设置过期时间
                .setExpiresAt(DateUtil.date(expMillis))
                .sign(JWTSignerUtil.hs256(ConstantPool.JWT_KEY.getBytes()));

        Map<String, Object> map = new HashMap<>();
        map.put("token", token);
        map.put("userCode", loginUser.getUser().getUserCode());
        map.put("userName", loginUser.getUser().getUserName());
        map.put("userAvatar", loginUser.getUser().getUserAvatar());
        map.put("userPerms", loginUser.getPermissions());

        // 把完整的用户信息存入redis，以userCode作为key
        redisCacheUtil.setCacheObject("login:" + loginUser.getUser().getUserCode(), loginUser);

        return ResultUtil.Success(ResultCodeEnum.SUCCESS.getCode(), "欢迎回来！！", map);
    }

    /**
     * 退出登录
     * @return ResultUtil
     */
    @Override
    public ResultUtil<?> logout() {
        // 获取SecurityContextHolder中的id
        UsernamePasswordAuthenticationToken authenticationToken = (UsernamePasswordAuthenticationToken) SecurityContextHolder.getContext().getAuthentication();
        LoginUser loginUser = (LoginUser) authenticationToken.getPrincipal();
        String userCode = loginUser.getUser().getUserCode();
        // 删除redis中的值
        redisCacheUtil.deleteObject("login:" + userCode);

        return ResultUtil.Success(ResultCodeEnum.SUCCESS.getCode(), "退出成功");
    }

    /**
     * 根据用户权限信息获取对应菜单树
     * @return ResultUtil
     */
    @Override
    public ResultUtil<?> getUserMenuTree() {
        // 获取用户权限信息
        UsernamePasswordAuthenticationToken authenticationToken = (UsernamePasswordAuthenticationToken) SecurityContextHolder.getContext().getAuthentication();
        LoginUser loginUser = (LoginUser) authenticationToken.getPrincipal();
        List<String> permissions = loginUser.getPermissions();

        // 通过权限信息获取对应菜单(或目录)
        LambdaQueryWrapper<Menu> lambdaQueryMenuWrapper = new LambdaQueryWrapper<>();
        lambdaQueryMenuWrapper.eq(Menu::getMenuStatus, 0)
                .ne(Menu::getMenuType, 2)
                .in(Menu::getMenuPerms, permissions);

        List<Menu> menuList = menuMapper.selectList(lambdaQueryMenuWrapper);

        // 生成菜单树
        List<MenuTreeVO> menuTree = menuList.stream()
                .filter(menu -> menu.getParentMenuId() == 0)
                .map(menu -> {
                    MenuTreeVO menuTreeVO = new MenuTreeVO();
                    BeanUtil.copyProperties(menu, menuTreeVO);
                    menuTreeVO.setChildren(getChildrenMenuTree(menuTreeVO, menuList));
                    return menuTreeVO;
                })
                .collect(Collectors.toList());

        return ResultUtil.Success(ResultCodeEnum.SUCCESS, menuTree);
    }

    /**
     * 递归生成菜单树
     *
     * @param menuTreeVO 菜单树VO
     * @param menuList   菜单集合
     * @return List
     */
    private List<MenuTreeVO> getChildrenMenuTree(MenuTreeVO menuTreeVO, List<Menu> menuList) {
        return menuList.stream()
                .filter(menu -> menu.getParentMenuId().equals(menuTreeVO.getId()))
                .map(menu -> {
                    MenuTreeVO menuTree = new MenuTreeVO();
                    BeanUtil.copyProperties(menu, menuTree);
                    menuTree.setChildren(getChildrenMenuTree(menuTree, menuList));
                    return menuTree;
                })
                .collect(Collectors.toList());
    }

    @Override
    public ResultUtil<?> getUserMenuData() {
        // 获取用户权限信息
        UsernamePasswordAuthenticationToken authenticationToken = (UsernamePasswordAuthenticationToken) SecurityContextHolder.getContext().getAuthentication();
        LoginUser loginUser = (LoginUser) authenticationToken.getPrincipal();
        List<String> permissions = loginUser.getPermissions();

        // 通过权限信息获取对应菜单(或目录)
        LambdaQueryWrapper<Menu> lambdaQueryMenuWrapper = new LambdaQueryWrapper<>();
        lambdaQueryMenuWrapper.eq(Menu::getMenuStatus, 0)
                .ne(Menu::getMenuType, 2)
                .in(Menu::getMenuPerms, permissions);

        List<Menu> menuList = menuMapper.selectList(lambdaQueryMenuWrapper);

        List<MenuDataVO> menuDataVOList = menuList.stream()
                .map(menu -> {
                    MenuDataVO menuDataVO = new MenuDataVO();
                    BeanUtil.copyProperties(menu, menuDataVO);
                    return menuDataVO;
                })
                .collect(Collectors.toList());

        return ResultUtil.Success(ResultCodeEnum.SUCCESS, menuDataVOList);
    }
}
